package ddz.db.dao;

import com.kaka.notice.annotation.Model;
import com.kaka.util.Charsets;
import com.kaka.util.MathUtils;
import ddz.db.Mybatis;
import ddz.db.mapper.UserSqlMapper;
import ddz.db.entity.UserInfo;
import ddz.db.redis.JedisFactory;
import ddz.db.redis.RedisHashCacher;
import ddz.db.redis.RedisLock;
import org.apache.ibatis.session.SqlSession;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;

import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;

/**
 * 玩家数据数据库操作
 *
 * @author zkpursuit
 */
@Model
public class UserDao extends RankDao {

    private Queue<UserInfo> add_update_queue = new ArrayBlockingQueue<>(2000);
    private RedisLock rankLock = new RedisLock();
    //private String rankKey = "win:rank";
    //private String lockKey = "win:rank:lock";

    private RedisHashCacher hashCacher = new RedisHashCacher();

    @Override
    protected void onRegister() {
        super.onRegister();
        hashCacher.init(UserInfo.class);
    }

    public UserDao() {
    }

    public UserInfo getUserInfoByUserId(Long uid) {
        try (SqlSession session = Mybatis.getInstance().getSqlSessionFactory().openSession()) {
            UserSqlMapper userSqlMapper = session.getMapper(UserSqlMapper.class);
            return userSqlMapper.getUserInfoByUserId(uid);
        }
    }

    private List<UserInfo> getUserInfoByUids(List<Long> uids) {
        try (SqlSession session = Mybatis.getInstance().getSqlSessionFactory().openSession()) {
            UserSqlMapper userSqlMapper = session.getMapper(UserSqlMapper.class);
            return userSqlMapper.getUserInfoByUids(uids);
        }
    }

    private String getOpenIdByUserId(Long uid) {
        try (SqlSession session = Mybatis.getInstance().getSqlSessionFactory().openSession()) {
            UserSqlMapper userSqlMapper = session.getMapper(UserSqlMapper.class);
            return userSqlMapper.getOpenIdByUid(uid);
        }
    }

    public void batchInsertUserInfo(List<UserInfo> list) {
        if (list == null || list.isEmpty()) return;
        try (SqlSession session = Mybatis.getInstance().getSqlSessionFactory().openSession()) {
            UserSqlMapper userSqlMapper = session.getMapper(UserSqlMapper.class);
            userSqlMapper.batchInsertUserInfo(list);
            session.commit();
        }
    }

    private List<UserInfo> getWinRankList() {
        try (SqlSession session = Mybatis.getInstance().getSqlSessionFactory().openSession()) {
            UserSqlMapper userSqlMapper = session.getMapper(UserSqlMapper.class);
            return userSqlMapper.getWinRankList();
        }
    }

    private byte[] key(long uid) {
        return ("user:k:" + uid).getBytes(Charsets.utf8);
    }

    public boolean exists(long uid) {
        JedisFactory factory = JedisFactory.getFactory();
        byte[] keyBytes = key(uid);
        try (Jedis jedis = factory.getJedis()) {
            if (jedis.exists(keyBytes)) return true;
        }
        try (SqlSession session = Mybatis.getInstance().getSqlSessionFactory().openSession()) {
            UserSqlMapper userSqlMapper = session.getMapper(UserSqlMapper.class);
            return userSqlMapper.existsUser(uid) > 0;
        }
    }

    private void writeToCache(UserInfo userInfo) {
        if (userInfo == null) return;
        byte[] keyBytes = key(userInfo.getUid());
        Map<byte[], byte[]> userFieldMap = hashCacher.toPropBytesMap(userInfo);
        try (Jedis jedis = JedisFactory.getFactory().getJedis()) {
            Pipeline pipeline = jedis.pipelined();
            pipeline.hmset(keyBytes, userFieldMap);
            pipeline.expire(keyBytes, 43200);
            pipeline.sync();
        }
    }

    private void writeToCache(long uid, Object... fields) {
        if (uid <= 0) return;
        if (fields.length == 0) return;
        if (fields.length % 2 != 0) return;
        byte[] keyBytes = key(uid);
        Map<byte[], byte[]> userFieldMap = hashCacher.toPropBytesMap(fields);
        try (Jedis jedis = JedisFactory.getFactory().getJedis()) {
            Pipeline pipeline = jedis.pipelined();
            pipeline.hmset(keyBytes, userFieldMap);
            pipeline.expire(keyBytes, 43200);
            pipeline.sync();
        }
    }

    public Map<Long, UserInfo> getUserInfoMap(Set<Long> uidList, String... fields) {
        List<UserInfo> list = getUserInfoList(uidList, fields);
        Map<Long, UserInfo> map = new HashMap<>(list.size());
        for (UserInfo info : list) {
            map.put(info.getUid(), info);
        }
        return map;
    }

    /**
     * 根据玩家ID列表获取玩家数据
     *
     * @param uidList 玩家ID列表
     * @return 玩家数据列表
     */
    public List<UserInfo> getUserInfoList(Set<Long> uidList, String... fields) {
        if (uidList == null || uidList.isEmpty()) return null;
        byte[][] keyArray = new byte[uidList.size()][];
        int i = 0;
        Iterator<Long> iterator = uidList.iterator();
        while (iterator.hasNext()) {
            Long uid = iterator.next();
            keyArray[i] = key(uid);
            ;
            i++;
        }
        List<UserInfo> list = new ArrayList<>(uidList.size());
        Set<Long> resultUidList;
        List<Object> resultList;
        JedisFactory factory = JedisFactory.getFactory();
        if (fields.length == 0) {
            try (Jedis jedis = factory.getJedis()) {
                Pipeline pipeline = jedis.pipelined();
                for (byte[] key : keyArray) {
                    pipeline.hgetAll(key);
                }
                resultList = pipeline.syncAndReturnAll();
            }
            resultUidList = new HashSet<>(resultList.size());
            //一条数据就是一个用户的所有数据
            for (Object result : resultList) {
                if (result == null) continue;
                Map<byte[], byte[]> fieldDataMap = (Map<byte[], byte[]>) result;
                if (!fieldDataMap.isEmpty()) {
                    UserInfo userInfo = new UserInfo();
                    fieldDataMap.forEach((byte[] fieldNameBytes, byte[] fieldValBytes) -> {
                        String fieldName = new String(fieldNameBytes, Charsets.utf8);
                        hashCacher.setFieldValue(userInfo, fieldName, fieldValBytes);
                    });
                    list.add(userInfo);
                    resultUidList.add(userInfo.getUid());
                }
            }
        } else {
            byte[][] fieldNameArray = new byte[fields.length][];
            boolean containUidField = false;
            for (int k = 0; k < fields.length; k++) {
                fieldNameArray[k] = fields[k].getBytes(Charsets.utf8);
                if ("uid".equals(fields[k])) {
                    containUidField = true;
                }
            }
            if (!containUidField) {
                throw new Error("必须包含uid字段");
            }
            try (Jedis jedis = factory.getJedis()) {
                Pipeline pipeline = jedis.pipelined();
                for (byte[] key : keyArray) {
                    pipeline.hmget(key, fieldNameArray);
                }
                resultList = pipeline.syncAndReturnAll();
            }
            resultUidList = new HashSet<>(resultList.size());
            //一条数据仅为一个用户的多个字段数据
            for (Object result : resultList) {
                if (result == null) continue;
                if (result instanceof List) {
                    List<byte[]> userDataList = (List<byte[]>) result;
                    boolean isAllNull = true; //是否全为空字段
                    for (byte[] data : userDataList) {
                        if (data != null) {
                            isAllNull = false;
                            break;
                        }
                    }
                    if (!isAllNull) { //有字段不为空才构建一条数据
                        UserInfo userInfo = new UserInfo();
                        for (int k = 0; k < fields.length; k++) {
                            String fieldName = fields[k];
                            byte[] fieldValBytes = userDataList.get(k);
                            if (fieldValBytes != null) {
                                hashCacher.setFieldValue(userInfo, fieldName, fieldValBytes);
                            }
                        }
                        if (userInfo.getUid() != null && userInfo.getUid() > 0) {
                            list.add(userInfo);
                            resultUidList.add(userInfo.getUid());
                        }
                    }
                }
            }
        }
        uidList.removeAll(resultUidList); //求差集
        if (!uidList.isEmpty()) {
            List<Long> uids = new ArrayList<>(uidList.size());
            uids.addAll(uidList);
            List<UserInfo> dbList = this.getUserInfoByUids(uids);
            if (!dbList.isEmpty()) {
                list.addAll(dbList);
                byte[][] uidKeyArray = new byte[dbList.size()][];
                List<Map<byte[], byte[]>> userFieldMapList = new ArrayList<>(uidKeyArray.length);
                for (i = 0; i < uidKeyArray.length; i++) {
                    UserInfo userInfo = dbList.get(i);
                    byte[] keyBytes = key(userInfo.getUid());
                    uidKeyArray[i] = keyBytes;
                    Map<byte[], byte[]> userFieldMap = hashCacher.toPropBytesMap(userInfo);
                    userFieldMapList.add(userFieldMap);
                }
                //int cacheMills = 3 * 24 * 60 * 60; //缓存三天
                int cacheMills = 43200;
                try (Jedis jedis = factory.getJedis()) {
                    Pipeline pipeline = jedis.pipelined();
                    for (i = 0; i < uidKeyArray.length; i++) {
                        pipeline.hmset(uidKeyArray[i], userFieldMapList.get(i));
                        pipeline.expire(uidKeyArray[i], cacheMills);
                    }
                    pipeline.sync();
                }
            }
        }
        return list;
    }

    private UserInfo getUserInfoFromCache(byte[] keyBytes, String... fields) {
        UserInfo userInfo = null;
        JedisFactory factory = JedisFactory.getFactory();
        if (fields.length == 0) {
            Map<byte[], byte[]> fieldDataMap;
            try (Jedis jedis = factory.getJedis()) {
                fieldDataMap = jedis.hgetAll(keyBytes);
            }
            if (fieldDataMap != null && !fieldDataMap.isEmpty()) {
                final UserInfo info = new UserInfo();
                fieldDataMap.forEach((byte[] fieldNameBytes, byte[] fieldValBytes) -> {
                    String fieldName = new String(fieldNameBytes, Charsets.utf8);
                    hashCacher.setFieldValue(info, fieldName, fieldValBytes);
                });
                userInfo = info;
            }
        } else {
            byte[][] fieldNameArray = new byte[fields.length][];
            boolean containUidField = false;
            for (int k = 0; k < fields.length; k++) {
                fieldNameArray[k] = fields[k].getBytes(Charsets.utf8);
                if ("uid".equals(fields[k])) {
                    containUidField = true;
                }
            }
            if (!containUidField) {
                throw new Error("必须包含uid字段");
            }
            List<byte[]> fieldValList;
            try (Jedis jedis = factory.getJedis()) {
                fieldValList = jedis.hmget(keyBytes, fieldNameArray);
            }
            if (fieldValList != null) {
                boolean isAllNull = true; //是否全为空字段
                for (byte[] data : fieldValList) {
                    if (data != null) {
                        isAllNull = false;
                        break;
                    }
                }
                if (!isAllNull) { //有字段不为空才构建一条数据
                    userInfo = new UserInfo();
                    for (int k = 0; k < fields.length; k++) {
                        String fieldName = fields[k];
                        byte[] fieldValBytes = fieldValList.get(k);
                        if (fieldValBytes != null) {
                            hashCacher.setFieldValue(userInfo, fieldName, fieldValBytes);
                        }
                    }
                }
            }
        }
        return userInfo;
    }

    public UserInfo getUserInfo(long uid, String... fields) {
        byte[] keyBytes = key(uid);
        UserInfo userInfo = this.getUserInfoFromCache(keyBytes, fields);
        if (userInfo == null) {
            userInfo = this.getUserInfoByUserId(uid);
            if (userInfo == null) return null;
            this.writeToCache(userInfo);
        }
        return userInfo;
    }

//    public void initWinRank() {
//        rankLock.lock(lockKey, 3000);
//        boolean already_init = false;
//        try (Jedis jedis = JedisFactory.getFactory().getJedis()) {
//            if (jedis.exists(rankKey)) {
//                already_init = true;
//            }
//        }
//        if (!already_init) {
//            List<UserInfo> userInfoList = getWinRankList();
//            int size = userInfoList.size();
//            if (size > 0) {
//                Map<String, Double> scoreMembers = new HashMap<>(size);
//                for (UserInfo userInfo : userInfoList) {
//                    scoreMembers.put(String.valueOf(userInfo.getUid()), (double) userInfo.getWinCount());
//                }
//                try (Jedis jedis = JedisFactory.getFactory().getJedis()) {
//                    jedis.zadd(rankKey, scoreMembers);
//                }
//            }
//        }
//        rankLock.unlock(lockKey);
//    }
//
//    public List<RankInfo> getUserWinRankList(Page page) {
//        rankLock.lock(lockKey, 3000);
//        List<RankInfo> list;
//        try {
//            list = this.getRankListFromRedis(rankKey, page);
//        } finally {
//            rankLock.unlock(lockKey);
//        }
//        this.bindUserInfo(list, this);
//        return list;
//    }
//
//    public RankInfo getRankInfoByUid(long uid) {
//        if (uid <= 0) return null;
//        RankInfo rankInfo = getUserRankInfoByUid(rankKey, uid);
//        return this.bindUserInfo(rankInfo, this);
//    }
//
//    /**
//     * 添加充值排行数据
//     *
//     * @param uid
//     */
//    public void addWinRankInfo(long uid, int winCount) {
//        String val = String.valueOf(uid);
//        rankLock.lock(lockKey, 3000);
//        try {
//            try (Jedis jedis = JedisFactory.getFactory().getJedis()) {
//                jedis.zadd(rankKey, winCount, val);
//            }
//        } finally {
//            rankLock.unlock(lockKey);
//        }
//    }

    public Map<String, String> getOpenidAndUnionidByUid(long uid) {
        byte[] keyBytes = key(uid);
        UserInfo info = getUserInfoFromCache(keyBytes);
        String openId;
        if (info == null) {
            info = this.getUserInfoByUserId(uid);
            if (info == null) return null;
            openId = info.getOpenid();
            this.writeToCache(info);
        } else {
            openId = getOpenIdByUserId(uid);
            if (openId != null) {
                info.setOpenid(openId);
                this.writeToCache(uid, "openid", openId);
            }
        }
        if (openId == null) return null;
        Map<String, String> map = new HashMap<>(2);
        map.put("openid", openId);
        map.put("unionid", info.getUnionid());
        return map;
    }

    public UserInfo insertOrUpdateUser(UserInfo info, String... field) {
        byte[] keyBytes = key(info.getUid());
        Map<byte[], byte[]> userFieldMap = hashCacher.toPropBytesMap(info, field);
//        List<byte[]> delFieldNameList = new ArrayList<>(userFieldMap.size());
//        userFieldMap.forEach((byte[] fname, byte[] fval) -> {
//            if (fval == null || fval.length == 0) {
//                delFieldNameList.add(fname);
//            }
//        });
//        byte[][] delFieldNameArray = null;
//        if (!delFieldNameList.isEmpty()) {
//            delFieldNameArray = new byte[delFieldNameList.size()][];
//            delFieldNameList.toArray(delFieldNameArray);
//        }
        try (Jedis jedis = JedisFactory.getFactory().getJedis()) {
            Pipeline pipeline = jedis.pipelined();
            pipeline.hmset(keyBytes, userFieldMap);
//            if (delFieldNameArray != null) {
//                pipeline.hdel(keyBytes, delFieldNameArray);
//            }
            pipeline.expire(keyBytes, 43200);
            pipeline.sync();
        }
        add_update_queue.add(info);
        return info;
    }

    private void processAddOrUpdateQueue(int count) {
        int size = add_update_queue.size();
        if (size < count) {
            count = size;
        }
        if (count == 0) ;
        Map<Long, Integer> idIdxMap = new HashMap<>(count);
        List<UserInfo> list = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            UserInfo info = add_update_queue.poll();
            if (!idIdxMap.containsKey(info.getUid())) {
                idIdxMap.put(info.getUid(), list.size());
                list.add(info);
            } else {
                int idx = idIdxMap.get(info.getUid());
                list.set(idx, info);
            }
        }
        this.batchInsertUserInfo(list);
    }

    public void processAddOrUpdateQueue(boolean flush) {
        if (flush) {
            while (!this.add_update_queue.isEmpty()) {
                processAddOrUpdateQueue(50);
            }
        } else {
            processAddOrUpdateQueue(50);
        }
    }

    public static final Set<Long> robot_uid = new HashSet<>();
    public static final Set<Long> playing_robot = new HashSet<>();

    public void initRobotUidList() {
        try (SqlSession session = Mybatis.getInstance().getSqlSessionFactory().openSession()) {
            UserSqlMapper userSqlMapper = session.getMapper(UserSqlMapper.class);
            List<Long> uidList = userSqlMapper.getRobotUidList();
            robot_uid.addAll(uidList);
        }
    }

    public void unsignRobot(Long... uid) {
        if (uid.length == 0) return;
        synchronized (this) {
            if (uid.length == 1) {
                playing_robot.remove(uid[0]);
            } else {
                for (int i = 0; i < uid.length; i++) {
                    playing_robot.remove(uid[i]);
                }
            }
        }
    }

    public Queue<Long> findRobot(int count) {
        synchronized (this) {
            Set<Long> all = new HashSet<>(robot_uid.size());
            all.addAll(robot_uid);
            all.removeAll(playing_robot);
            if (all.isEmpty()) return null;
            List<Long> list = new ArrayList<>(all.size());
            list.addAll(all);
            if (all.size() <= count) {
                return new LinkedList<>(list);
            }
            Set<Long> set = new HashSet<>(count);
            int i = 0, max = all.size() * 10;
            final int randMin = 0, randMax = all.size() - 1;
            while (i < max) {
                int idx = MathUtils.random(randMin, randMax);
                Long uid = list.get(idx);
                if (!set.contains(uid)) {
                    set.add(uid);
                    if (set.size() == count) break;
                }
                i++;
            }
            playing_robot.addAll(set);
            return new LinkedList<>(set);
        }
    }

//    private static final byte[] sign_robot_key = "robot:playing".getBytes(Charsets.utf8);
//
//    public void signRobot(long... uid) {
//        if (uid.length == 1) {
//            try (Jedis jedis = JedisFactory.getFactory().getJedis()) {
//                jedis.sadd(sign_robot_key, ByteUtils.getBytes(uid[0]));
//            }
//            return;
//        }
//        byte[][] members = new byte[uid.length][];
//        for (int i = 0; i < members.length; i++) {
//            members[i] = ByteUtils.getBytes(uid[i]);
//        }
//        try (Jedis jedis = JedisFactory.getFactory().getJedis()) {
//            jedis.sadd(sign_robot_key, members);
//        }
//    }
//
//    public void unsignRobot(long... uid) {
//        if (uid.length == 1) {
//            try (Jedis jedis = JedisFactory.getFactory().getJedis()) {
//                jedis.sadd(sign_robot_key, ByteUtils.getBytes(uid[0]));
//            }
//            return;
//        }
//        byte[][] members = new byte[uid.length][];
//        for (int i = 0; i < members.length; i++) {
//            members[i] = ByteUtils.getBytes(uid[i]);
//        }
//        try (Jedis jedis = JedisFactory.getFactory().getJedis()) {
//            jedis.sadd(sign_robot_key, members);
//        }
//    }

}
